﻿using System;
using Nahida.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class NPRBloomPass : ScriptableRenderPass
{
    public enum SampleQuality
    {
        Half,
        Quarter
    }

    enum Pass
    {
        BloomPrefilter,
        BloomHorizontalBlur1x,
        BloomHorizontalBlur2x,
        BloomVerticalBlur1x,
        BloomVerticalBlur2x,
        BloomUpsample,
        ColorGrading
    }
    /// <summary> 渲染材质 </summary>
    readonly Material _material;
    
    readonly int _bloomThreshold = Shader.PropertyToID("_BloomThreshold");
    readonly int _bloomIntensity = Shader.PropertyToID("_BloomIntensity");
    readonly int _blurRadius = Shader.PropertyToID("_BlurRadius");

    readonly int _bloomTextureA = Shader.PropertyToID("_BloomTextureA");
    readonly int _bloomTextureB = Shader.PropertyToID("_BloomTextureB");
    readonly int _bloomTextureC = Shader.PropertyToID("_BloomTextureC");
    readonly int _bloomTextureD = Shader.PropertyToID("_BloomTextureD");

    // 最大RT数量
    const int MaxPyramidSize = 4;
    readonly RTHandle[] _bloomMipUp;
    readonly RTHandle[] _bloomMipDown;
    
    public NPRBloomPass(Shader shader)
    {
        _material = CoreUtils.CreateEngineMaterial(shader);

        _bloomMipUp = new RTHandle[MaxPyramidSize];
        _bloomMipDown = new RTHandle[MaxPyramidSize];
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        // ------ 获取后处理组件 ------
        var stack = VolumeManager.instance.stack;
        var bloomVolume = stack.GetComponent<NPRBloom>();

        if (!bloomVolume.IsActive()) return;

        // ------ 初始化 ------
        var cameraDescriptor = renderingData.cameraData.cameraTargetDescriptor;
        var descriptor = new RenderTextureDescriptor(cameraDescriptor.width, cameraDescriptor.height, cameraDescriptor.graphicsFormat, 0, 0);

        var _sampleScale = bloomVolume.sampleQuality.value == SampleQuality.Half ? 0.5f : 0.25f;
        descriptor.width = (int)Math.Round(descriptor.width * _sampleScale);
        descriptor.height = (int)Math.Round(descriptor.height * _sampleScale);

        for (var i = 0; i < MaxPyramidSize; i++)
        {
            RenderingUtils.ReAllocateIfNeeded(ref _bloomMipUp[i], descriptor, FilterMode.Bilinear, name: "_BloomMipUp{i}");
            RenderingUtils.ReAllocateIfNeeded(ref _bloomMipDown[i], descriptor, FilterMode.Bilinear, name: "_BloomMipDown{i}");
            descriptor.width >>= 1;
            descriptor.height >>= 1;
        }

        // ------ 设置属性 ------
        _material.SetFloat(_bloomThreshold, bloomVolume.threshold.value);
        _material.SetFloat(_bloomIntensity, bloomVolume.intensity.value);

        // 自适应计算
        var screenAdaptation = _sampleScale * renderingData.cameraData.cameraTargetDescriptor.height / 1080f;
        _material.SetFloat(_blurRadius, bloomVolume.blurRadius.value * screenAdaptation);

        // 渲染Buffer
        var cmd = CommandBufferPool.Get("--- NPR Bloom ---");
        var sourceRT = renderingData.cameraData.renderer.cameraColorTargetHandle;

        RTHandle[] bufferUp = _bloomMipUp;
        RTHandle[] bufferDown = _bloomMipDown;

        SetBlit(cmd, sourceRT, bufferUp[0], Pass.BloomPrefilter);

        SetBlit(cmd, bufferUp[0], bufferDown[0], Pass.BloomHorizontalBlur1x);
        SetBlit(cmd, bufferDown[0], bufferUp[0], Pass.BloomVerticalBlur1x);

        for (var i = 1; i < MaxPyramidSize; i++)
        {
            SetBlit(cmd, bufferUp[i - 1], bufferDown[i], Pass.BloomHorizontalBlur2x);
            SetBlit(cmd, bufferDown[i], bufferUp[i], Pass.BloomVerticalBlur1x);
        }

        cmd.SetGlobalTexture(_bloomTextureA, bufferUp[0]);
        cmd.SetGlobalTexture(_bloomTextureB, bufferUp[1]);
        cmd.SetGlobalTexture(_bloomTextureC, bufferUp[2]);
        cmd.SetGlobalTexture(_bloomTextureD, bufferUp[3]);

        SetBlit(cmd, bufferDown[0], bufferDown[0], Pass.BloomUpsample);
        cmd.SetGlobalTexture(_bloomTextureA, bufferDown[0]);

        Blit(cmd, ref renderingData, _material, (int)Pass.ColorGrading);

        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    private void SetBlit(CommandBuffer commandBuffer, RTHandle sourceRT, RTHandle targetRT, Pass shaderPass)
    {
        Blitter.BlitCameraTexture(commandBuffer, sourceRT, targetRT, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, _material, (int)shaderPass);
    }

    public void OnDispose()
    {
        for (var i = 0; i < MaxPyramidSize; i++)
        {
            _bloomMipUp[i]?.Release();
            _bloomMipDown[i]?.Release();
        }

        CoreUtils.Destroy(_material);
    }
}